package com.yjz.middleware.RpcFrameWork;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @author :yuanJinZhong
 * @description:手写rpc框架
 * @date :2018/6/19-14:24
 */
public class RpcFramework {

  /**
   * 暴露服务
   *
   * @param service 服务实现
   * @param port    服务端口
   * @throws IOException
   */
  public static void export(final Object service, int port) throws IOException {
    if (service == null) {
      throw new IllegalArgumentException("service instance==null");
    }
    if (port < 0 || port > 65535) {
      throw new IllegalArgumentException("invalid port:" + port);
    }
    System.out.println("export service " + service.getClass().getName() + "on port" + port);
    ServerSocket server = new ServerSocket(port);
    for (; ; ) {
      System.out.println("循环监听" + port + "端口的请求");
      final Socket socket = server.accept();
      new Thread(() -> {
        try {
          ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
          System.out.println("step2:" + System.currentTimeMillis());
          /**
           *  提供短
           * 从流里面获取：请求参数，请求的方法名，请求的参数类型
           */
          String methodName = input.readUTF();
          Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
          Object[] arguments = (Object[]) input.readObject();
          ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
          Method method = service.getClass().getMethod(methodName, parameterTypes);
          Object result = method.invoke(service, arguments);
          System.out.println("提供端根据消费端传进流里面的参数，反射调用方法，得到方法请求结果：" + result.toString());
          output.writeObject(result);
          System.out.println("step3:" + System.currentTimeMillis());

        } catch (IOException e) {
          e.printStackTrace();
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        } catch (NoSuchMethodException e) {
          e.printStackTrace();
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        } catch (InvocationTargetException e) {
          e.printStackTrace();
        }
      }).start();
    }
    /**
     *对象输出流和对象输入流，以及socket都没有关闭
     */

  }

  /**
   * 引用服务
   *
   * @param <T>            接口泛型
   * @param interfaceClass 接口类型
   * @param host           服务器主机名
   * @param port           服务器端口
   * @return 远程服务
   */
  public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) {
    if (interfaceClass == null) {
      throw new IllegalArgumentException("Interface class == null");
    }
    if (!interfaceClass.isInterface()) {
      throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
    }
    if (host == null || host.length() == 0) {
      throw new IllegalArgumentException("Host == null!");
    }
    if (port <= 0 || port > 65535) {
      throw new IllegalArgumentException("Invalid port " + port);
    }
    System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
    return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, (proxy, method, arguments) -> {
      Socket socket = new Socket(host, port);
      try {
        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
        try {
          /**
           *  消费端
           *  将请求参数，请求的方法名，请求的参数类型，写入ObjectOutputStream,供服务端获取
           */
          output.writeUTF(method.getName());
          output.writeObject(method.getParameterTypes());
          output.writeObject(arguments);
          System.out.println("step1:" + System.currentTimeMillis());
          ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
          try {
            System.out.println("step4:" + System.currentTimeMillis());
            Object result = input.readObject();
            if (result instanceof Throwable) {
              throw (Throwable) result;
            }
            return result;
          } finally {
            input.close();
          }
        } finally {
          output.close();
        }
      } finally {
        socket.close();
      }
    });
  }

}
